using System;
using System.IO;

using Microsoft.SPOT;

using OST.Shared;
using OST.Shared.Extensions;
using OST.Shared.Interfaces;
using OST.Shared.SharedTypes;
using OST.Persistence.Extensions;

using mfLogger;


namespace OST.Persistence
{
    /// <summary>
    /// provides an persistant instance of <see cref="Configuration"/> synchronized with storage
    /// </summary>
    public class ConfigurationProvider : IConfigurationProvider
    {
        #region Consts&Fields

        /// <summary>
        /// ost configuration filename
        /// </summary>
        private const string C_STR_CONFIG_FILENAME = "OSTConfiguration.xml";

        #endregion

        #region CTOR// Singleton
        //http://msdn.microsoft.com/en-us/library/ff650316.aspx
        //--> not thread-safe, with dynamic initialization

        private static ConfigurationProvider _instance = null;

        private ConfigurationProvider()
        {
            Storage.PropertyChanged += Storage_PropertyChanged;
        }

        public static ConfigurationProvider Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new ConfigurationProvider();
                }

                return _instance;
            }
        }

        #endregion

        #region Properties

        private IStorageProvider Storage
        {
            get
            {
                return RemoveableMediaStorageProvider.Instance;
            }
        }

        /// <summary>
        /// wrapper-property 4 logging
        /// </summary>
        private ILoggerAppender Logger
        {
            get
            {
                return mfLogger.mfLogger.Instance;
            }
        }

        #endregion

        #region Methods// Events

        void Storage_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "IsStorageConntected"
                && ((bool)e.PropertyNewValue))
            {
                ReadConfiguration();
                Apply();
            }
        }

        #endregion

        #region IConfigurationProvider

        public void Initialize()
        {
            Logger.Debug("config-provider initialized");
        }

        /// <summary>
        /// actual config-instance
        /// </summary>
        public Configuration ConfigInstance { get; set; }

        /// <summary>
        /// flag, checks if a config-instance is available
        /// </summary>
        public bool HasConfig { get { return ConfigInstance != null; } }

        public bool IsConfigApplied { get; private set; }

        public void ReadConfiguration()
        {
            try
            {
                if (!Storage.IsStorageConntected)
                    throw new OSTException("no storage connected for read-config");

                var configFilePath = Path.Combine(Storage.RootDirectory, C_STR_CONFIG_FILENAME);

                //first read xml 2 string to spare memory-load @one-time-point
                var configStream = new StreamReader(configFilePath);
                var configString = configStream.ReadToEnd();
                configStream.Close();
                configStream.Dispose();
                
                //remove whitespace-characters
                configString = configString.RemoveStrings(new string[] {" ", "\n", "\r\n", "\t" });

                Logger.Debug("config file readed, with " + Debug.GC(true) + "free bytes");

                var confSerializer = new mfXmlSerializer.mfXmlTypedSerializer(typeof(Configuration));
                ConfigInstance = (Configuration)confSerializer.Deserialize(configString);
                confSerializer.Dispose();

                ConfigInstance.Validate();

                Logger.Info("config loaded");
            }
            catch (OutOfMemoryException ex)
            {
                Logger.Error(ex);
                ConfigInstance = new Configuration();
                Logger.Warning("read config failed caus out-of-memory-exception, default-config is used, please check <Configuration/> Xml Format externally");
            }
            catch (Exception ex)
            {
                Logger.Error(ex);
                ConfigInstance = new Configuration();
                Logger.Warning("read config failed, default-config is used");
            }
            finally 
            {
                Debug.GC(true);
            }
        }

        public void SaveConfiguration()
        {
            try
            {
                Logger.Debug("config saving");

                if (!Storage.IsStorageConntected)
                    throw new OSTException("no storage connected for save-config");

                var configFilePath = Path.Combine(Storage.RootDirectory, C_STR_CONFIG_FILENAME);

                var confSerializer = new mfXmlSerializer.mfXmlTypedSerializer(typeof(Configuration));

                var configFileStream = new StreamWriter(configFilePath, false);

                confSerializer.SerializeOnStream(ConfigInstance, configFileStream.BaseStream);

                configFileStream.Close(); 
                configFileStream.Dispose();

                Logger.Info("config saved");
            }
            catch (Exception ex)
            {
                Logger.Error(ex);
            }
            finally
            {
                Debug.GC(true);
            }
        }

        public void Apply()
        {
            try
            {
                ConfigInstance.Logging.ApplyLogging();
                ConfigInstance.Ethernet.ApplyEthernet();
                ConfigInstance.TimeService.ApplyTimeService();

                Logger.Debug("config applied");

                IsConfigApplied = true;
                OnNotifyPropertyChanged("IsConfigApplied", IsConfigApplied);
            }
            catch (Exception ex)
            {
                Logger.Error(ex);
            }
        }

        #endregion

        #region INotifyPropertyChanged

        /// <summary>
        /// notifies clients over changes in puplic properties
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnNotifyPropertyChanged(String propertyName, object newValue)
        {

            if (PropertyChanged != null && !propertyName.IsNullOrEmpty())
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName, newValue));
            }
        }

        #endregion

    }
}
